package com.tuuzed.hydrogen.internal;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.tuuzed.hydrogen.Model;
import com.tuuzed.hydrogen.UpgradeCallback;
import com.tuuzed.hydrogen.annotation.Table;
import com.tuuzed.hydrogen.internal.util.StringUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import dalvik.system.DexFile;

public final class HydrogenContext {
    private SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mWritableDatabase;
    private SQLiteDatabase mReadableDatabase;

    private Map<Class<? extends Model>, List<Property>> mProperties;
    private Map<Class<? extends Model>, String> mTableNames;
    private Set<Class<? extends Model>> mModelClasses;

    private HydrogenConfig mHydrogenConfig;

    private static HydrogenContext DEFAULT;

    private HydrogenContext() {
    }

    public static HydrogenContext getDefault() {
        if (DEFAULT == null) {
            synchronized (HydrogenContext.class) {
                if (DEFAULT == null) {
                    DEFAULT = new HydrogenContext();
                }
            }
        }
        return DEFAULT;
    }

    public synchronized void initialize(@NonNull Context context,
                                        @Nullable SQLiteDatabase.CursorFactory factory,
                                        @Nullable final UpgradeCallback callback) {
        mHydrogenConfig = HydrogenConfig.get(context);
        initModelClasses(context, mHydrogenConfig.getModelPackage());
        mDatabaseHelper = new SQLiteOpenHelper(context, mHydrogenConfig.getDatabaseName(), factory, mHydrogenConfig.getDatabaseVersion()) {
            @Override
            public void onCreate(SQLiteDatabase db) {
                for (Class<? extends Model> clazz : mModelClasses) {
                    String createTableSql = getCreateTableSql(clazz);
                    SQLiteHelper.execSQL(db, createTableSql);
                }
            }

            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                if (db.needUpgrade(newVersion) && callback != null) {
                    String upgradeSQL = callback.getUpgradeSQL(oldVersion, newVersion);
                    if (!TextUtils.isEmpty(upgradeSQL)) {
                        SQLiteHelper.execSQL(db, upgradeSQL);
                    }
                }
            }
        };
    }

    public synchronized void destroy() {
        if (mWritableDatabase != null) {
            if (mWritableDatabase.isOpen()) {
                mWritableDatabase.close();
            }
            mWritableDatabase = null;
        }
        if (mReadableDatabase != null) {
            if (mReadableDatabase.isOpen()) {
                mReadableDatabase.close();
            }
            mReadableDatabase = null;
        }
        if (mDatabaseHelper != null) {
            mDatabaseHelper.close();
            mDatabaseHelper = null;
        }
        mProperties = null;
        mTableNames = null;
        mModelClasses = null;
        mHydrogenConfig = null;
        DEFAULT = null;
    }

    public synchronized SQLiteDatabase getWritableDatabase() {
        if (mWritableDatabase != null && mWritableDatabase.isOpen()) {
            return mWritableDatabase;
        }
        return mWritableDatabase = newWritableDatabase();
    }

    public synchronized SQLiteDatabase getReadableDatabase() {
        if (mReadableDatabase != null && mReadableDatabase.isOpen()) {
            return mReadableDatabase;
        }
        return mReadableDatabase = newReadableDatabase();
    }

    @NonNull
    public String getTableName(@NonNull Class<? extends Model> clazz) {
        if (mTableNames == null) mTableNames = new HashMap<>();
        String tableName = mTableNames.get(clazz);
        if (tableName == null) {
            Table table = clazz.getAnnotation(Table.class);
            if (table == null) {
                tableName = StringUtils.camelToUnderline(clazz.getSimpleName());
            } else {
                tableName = StringUtils.camelToUnderline(table.value());
            }
            mTableNames.put(clazz, tableName);
        }
        return tableName;
    }

    @NonNull
    public List<Property> getProperties(Class<? extends Model> clazz) {
        if (mProperties == null) mProperties = new HashMap<>();
        List<Property> properties = mProperties.get(clazz);
        if (properties == null) {
            properties = new ArrayList<>();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                Property property = Property.create(field);
                if (property != null) properties.add(property);
            }
            mProperties.put(clazz, properties);
        }
        return properties;
    }

    boolean isEchoSQL() {
        return mHydrogenConfig.isEchoSQL();
    }

    // 获取建表语句
    private String getCreateTableSql(Class<? extends Model> clazz) {
        StringBuilder sb = new StringBuilder();
        sb.append("CREATE TABLE IF NOT EXISTS ")
                .append(getTableName(clazz))
                .append("(_id INTEGER PRIMARY KEY AUTOINCREMENT,");
        boolean first = true;
        for (Property property : getProperties(clazz)) {
            if (!first) sb.append(",");
            sb.append(property.getColumnName()).append(" ").append(property.getColumnType());
            sb.append(property.isNullable() ? " NULL" : " NOT NULL");
            sb.append(property.isUnique() ? " UNIQUE" : "");
            first = false;
        }
        sb.append(")");
        return sb.toString();
    }

    private synchronized SQLiteDatabase newWritableDatabase() {
        return mDatabaseHelper.getWritableDatabase();
    }

    private synchronized SQLiteDatabase newReadableDatabase() {
        return mDatabaseHelper.getWritableDatabase();
    }

    private void initModelClasses(Context context, String packageName) {
        if (mModelClasses == null) {
            mModelClasses = new HashSet<>();
        }
        DexFile dexFile = null;
        try {
            dexFile = new DexFile(context.getPackageCodePath());
            Enumeration<String> entries = dexFile.entries();
            while (entries.hasMoreElements()) {
                String element = entries.nextElement();
                if (element.startsWith(packageName)) {
                    try {
                        Class<?> clazz = Class.forName(element);
                        if (clazz.getSuperclass() == Model.class) {
                            //noinspection unchecked
                            mModelClasses.add((Class<? extends Model>) clazz);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dexFile != null) {
                try {
                    dexFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
